﻿using System.Threading.Channels;

using Microsoft.AspNetCore.SignalR;
using System;
using System.Runtime.CompilerServices;

namespace SignalRServerDemo.Hubs
{
    public class StreamingHub : Hub
    {
        private readonly ILogger<StreamingHub> _logger;
        private readonly IHostEnvironment _environment;

        public StreamingHub(ILogger<StreamingHub> logger, IHostEnvironment environment)
        {
            _logger = logger;
            _environment = environment;
        }

        public override Task OnConnectedAsync()
        {
            _logger.LogInformation($"建立连接，连接编号：{Context.ConnectionId} - {DateTime.Now}");
            return base.OnConnectedAsync();
        }

        public override Task OnDisconnectedAsync(Exception? exception)
        {
            if (exception != null)
            {
                _logger.LogError(exception, $"断开连接，连接编号：{Context?.ConnectionId} - {DateTime.Now}");
            }
            else
            {
                _logger.LogInformation($"断开连接，连接编号：{Context?.ConnectionId} - {DateTime.Now}");
            }

            return base.OnDisconnectedAsync(exception);
        }

        /// <summary>
        /// 上传图片
        /// </summary>
        public async Task UploadImage(ChannelReader<string> stream)
        {
            Console.WriteLine($"文件传输已接入，连接编号：{Context?.ConnectionId} - {DateTime.Now}");
            string? fileName = string.Empty;
            if (await stream.WaitToReadAsync())
            {
                stream.TryRead(out fileName);
            }
            if (string.IsNullOrWhiteSpace(fileName)) return;
            Console.WriteLine(fileName);

            string fullDirName = Path.Combine(_environment.ContentRootPath, "wwwroot", "upload");
            Directory.CreateDirectory(fullDirName);
            string fullFileName = Path.Combine(fullDirName, fileName);

            if (File.Exists(fullFileName)) File.Delete(fullFileName);

            using FileStream fs = new FileStream(fullFileName, FileMode.CreateNew, FileAccess.Write, FileShare.Delete);
            while (await stream.WaitToReadAsync())
            {
                while (stream.TryRead(out var item))
                {
                    // do something with the stream item
                    Console.WriteLine(item);
                    byte[] buffer = Convert.FromBase64String(item);
                    await fs.WriteAsync(buffer);
                }
            }
            Console.WriteLine($"文件传输已结束，连接编号：{Context?.ConnectionId} - {DateTime.Now}");
        }

        /// <summary>
        /// 流读取与返回（无法同步返回）
        /// </summary>
        public async Task StreamingReadWithReturn(ChannelReader<string> stream)
        {
            HashSet<string> datas = new HashSet<string>();
            while (await stream.WaitToReadAsync())
            {
                while (stream.TryRead(out var item))
                {
                    // do something with the stream item
                    Console.WriteLine(item);
                    datas.Add(item);
                }
            }

            string msg = string.Join(Environment.NewLine, datas);
            await Clients.Caller.SendAsync("ClientHook", msg);
        }

        /// <summary>
        /// 迭代返回
        /// </summary>
        public async IAsyncEnumerable<int> EnumerableReturn(
            int count,
            int delay,
            [EnumeratorCancellation]
            CancellationToken cancellationToken)
        {
            for (var i = 0; i < count; i++)
            {
                // Check the cancellation token regularly so that the server will stop
                // producing items if the client disconnects.
                cancellationToken.ThrowIfCancellationRequested();

                yield return i;

                // Use the cancellationToken in other APIs that accept cancellation
                // tokens so the cancellation can flow down to them.
                await Task.Delay(delay, cancellationToken);
            }
        }

        /// <summary>
        /// 渠道回写
        /// </summary>
        public ChannelReader<int> ChannelWrite(
            int count,
            int delay,
            CancellationToken cancellationToken)
        {
            var channel = Channel.CreateUnbounded<int>();
            _ = InnerChannelWrite(channel.Writer, count, delay, cancellationToken);
            return channel.Reader;
        }

        private async Task InnerChannelWrite(
            ChannelWriter<int> writer,
            int count,
            int delay,
            CancellationToken cancellationToken)
        {
            Exception? localException = null;
            try
            {
                for (var i = 0; i < count; i++)
                {
                    await writer.WriteAsync(i, cancellationToken);

                    // Use the cancellationToken in other APIs that accept cancellation
                    // tokens so the cancellation can flow down to them.
                    await Task.Delay(delay, cancellationToken);
                }
            }
            catch (Exception ex)
            {
                localException = ex;
            }
            finally
            {
                writer.Complete(localException);
            }
        }
    }
}

// https://github.com/dotnet/AspNetCore.Docs/tree/live/aspnetcore/signalr/streaming/samples/3.0
// https://www.cnblogs.com/dongh/p/16457037.html
// https://blog.csdn.net/ma_jiang/article/details/126599979
// https://codereview.stackexchange.com/questions/242344/uploading-files-through-streaming-with-signalr
// https://damienbod.com/2018/05/13/uploading-and-sending-image-messages-with-asp-net-core-signalr/
// https://github.com/damienbod/AspNetCoreAngularSignalR
